// Helper for grab operations.

#include "GrabManager.h"
#include "IniManager.h"
#include <iostream>
#include <future>

GrabManager GrabManager::m_instance = GrabManager();

// Start the grab manager.
void GrabManager::Start(GrabOptions& grabOptions)
{
	// load the options
	m_instance.LoadOptions(grabOptions);

	// show the help
	m_instance.ShowHelp();

	m_instance.m_run = true;

	// hook the keyboard
	MSG msg;
	m_instance.m_hHock = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelHook, NULL, NULL);
	m_instance.m_hooked = true;

	while(m_instance.m_run && !GetMessage(&msg, NULL, NULL, NULL)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

// Stop the grab manager.
void GrabManager::Stop()
{
	m_instance.m_run = false;
	m_instance.~GrabManager();
}

// Construct GrabManager.
GrabManager::GrabManager()
{
	m_hooked = m_manualGrab = m_tryToCapture = false;
	m_pixelsToScan = m_sensibilityLevel = m_startSleep = m_startAfterInterval = m_startAfterRandomInterval = m_tryInterval = m_clicksPerTry = 0;
	m_hHock = NULL;
}

// Finalize GrabManager.
GrabManager::~GrabManager()
{
	if (m_hooked)
	{
		UnhookWindowsHookEx(m_instance.m_hHock);
		m_hooked = false;
	}
}

// Load the options.
void GrabManager::LoadOptions(GrabOptions& grabOptions)
{
	m_inputHelper = InputHelper(grabOptions.InputOptions);
	m_autoGrab = false;
	m_manualGrab = false;
	m_tryToCapture = false;
	m_pixelsToScan = grabOptions.PixelsToScan;
	m_sensibilityLevel = grabOptions.SensibilityLevel;
	m_startAfterInterval = grabOptions.StartAfterInterval;
	m_startAfterRandomInterval = grabOptions.StartAfterRandomInterval;
	m_tryInterval = grabOptions.TryInterval;
	m_clicksPerTry = grabOptions.ClicksPerTry;
	m_coordinates = grabOptions.ScanCoordinates;
	m_coordinatesToScan = vector<POINT>(grabOptions.ScanCoordinates.begin(), grabOptions.ScanCoordinates.begin() + grabOptions.PixelsToScan);
}

// Show the help.
void GrabManager::ShowHelp()
{
	wcout << YELLOW << L"[Ctrl]+[F1]" << LIGHTGREY << L"        => show this help.\n";
	wcout << YELLOW << L"[Ctrl]+[F2]" << LIGHTGREY << L"        => " << LIGHTGREEN << L"start" << LIGHTGREY << L" the " << CYAN << L"Manual" << LIGHTGREY << L" grab process.\n";
	wcout << YELLOW << L"[Ctrl]+[F3]" << LIGHTGREY << L"        => " << LIGHTRED << L"stop" << LIGHTGREY << L" the " << CYAN << L"Manual" << LIGHTGREY << L" grab process.\n";
	wcout << YELLOW << L"[Ctrl]+[F4]" << LIGHTGREY << L"        => " << LIGHTRED << L"switch" << LIGHTGREY << L" (ON or OFF) the " << CYAN << L"Automatic" << LIGHTGREY << L" grab process.\n";
	wcout << YELLOW << L"[Ctrl]+[F5]" << LIGHTGREY << L"        => " << CYAN << L"set" << LIGHTGREY << L" the cursor position.\n";
	wcout << YELLOW << L"[Ctrl]+[F6]" << LIGHTGREY << L"        => " << CYAN << L"get" << LIGHTGREY << L" the cursor position.\n";
	wcout << YELLOW << L"[Ctrl]+[F8]" << LIGHTGREY << L"        => " << LIGHTGREY << L"reload the configuration file.\n";
	wcout << YELLOW << L"[Ctrl]+[+]" << LIGHTGREY << L"         => " << LIGHTGREEN << L"raise" << LIGHTGREY << L" the scanner sensibility.\n";
	wcout << YELLOW << L"[Ctrl]+[-]" << LIGHTGREY << L"         => " << LIGHTRED << L"lower" << LIGHTGREY << L" the scanner sensibility.\n";
	wcout << YELLOW << L"[Ctrl]+[Shift]+[+]" << LIGHTGREY << L" => " << LIGHTGREEN << L"increase" << LIGHTGREY << L" the number of pixels to scan.\n";
	wcout << YELLOW << L"[Ctrl]+[Shift]+[-]" << LIGHTGREY << L" => " << LIGHTRED << L"decrease" << LIGHTGREY << L" the number of pixels to scan.\n";
}

// Switch the automatic grab process mode.
bool GrabManager::SwitchAutoGrab()
{
	if (m_manualGrab) return LogManager::DisplayError(L"You cannot start the automatic grabber while the manual grabber is started.\n");
	if (m_autoGrab)
	{
		m_autoGrab = false;
		wcout << L"Automatic grabbing " << LIGHTRED << L"stopped" << LIGHTGREY << L" ...\n";
	}
	else
	{
		m_autoGrab = true;
		CalculateStartSleep();
		wcout << L"Automatic grabbing " << LIGHTGREEN << L"started" << LIGHTGREY << L" ...\n";
		async (&GrabManager::AutoGrabTask, this);
	}
	return true;
}

// Start the manual grab process.
bool GrabManager::StartManualGrab()
{
	if (m_autoGrab) return LogManager::DisplayError(L"You cannot start the manual grabber while the automatic grabber is started.\n");
	if (m_manualGrab) return LogManager::DisplayError(L"The manual grabber is already started.\n");
	m_manualGrab = true;
	CalculateStartSleep();
	wcout << L"Manual grabbing " << LIGHTGREEN << L"started" << LIGHTGREY << L" ...\n";
	async (&GrabManager::ManualGrabTask, this);
	return true;
}

// Stop the manual grab process.
bool GrabManager::StopManualGrab()
{
	if (!m_manualGrab) return LogManager::DisplayError(L"The manual grabber is already stopped.\n");
	m_manualGrab = false;
	m_tryToCapture = false;
	wcout << L"Manual grabbing " << LIGHTRED << L"stopped" << LIGHTGREY << L" ...\n";
	return true;
}

// Set the cursor position.
void GrabManager::SetCursorPosition()
{
	m_inputHelper.SetCursorPosition();
	LogManager::DisplayMessage(L"Cursor position set.\n");
}

// Get the cursor position.
void GrabManager::GetCursorPosition()
{
	POINT coordinates;
	if (m_inputHelper.GetCursorPosition(coordinates))
		LogManager::DisplayMessage(L"Cursor coordinates : X = %d ; Y = %d.\n", coordinates.x, coordinates.y);
}

// v the number of pixels to scan.
bool GrabManager::IncreasePixelsToScan()
{
	if (m_pixelsToScan >= m_coordinates.size()) return LogManager::DisplayError(L"You cannot scan more pixels than defined (%d)  in your configuration file in order to detect the popup.\n", m_coordinates.size());
	m_coordinatesToScan.push_back(m_coordinates.at(m_pixelsToScan));
	m_pixelsToScan++;
	return LogManager::DisplayMessage(L"%d pixels will be scanned for popup detection.\n", m_pixelsToScan);
}

// Decrease the number of pixels to scan.
bool GrabManager::DecreasePixelsToScan()
{
	if (m_pixelsToScan <= 0) return LogManager::DisplayError(L"You must scan at least 1 pixel in order to detect the popup.\n");
	m_coordinatesToScan.pop_back();
	m_pixelsToScan--;
	return LogManager::DisplayMessage(L"%d pixel%s will be scanned for popup detection.\n", m_pixelsToScan, m_pixelsToScan == 1 ? "" : "s");
}

// Raise the sensibility.
void GrabManager::RaiseSensibilityLevel()
{
	m_inputHelper.Sensibility += m_sensibilityLevel;
	LogManager::DisplayMessage(L"Sensibility level was raised to %d.\n", m_inputHelper.Sensibility);
}

// Lower the sensibility.
void GrabManager::LowerSensibilityLevel()
{
	m_inputHelper.Sensibility -= m_sensibilityLevel;
	LogManager::DisplayMessage(L"Sensibility level was lowered to %d.\n", m_inputHelper.Sensibility);
}

// Reload the configuration.
bool GrabManager::ReloadConfiguration()
{
	if (m_manualGrab || m_autoGrab) return LogManager::DisplayError(L"You cannot reload the configuration file while grabbing.\n");
	Configuration configuration;
	if (!IniManager::Read(__CONFIGURATION_INI_PATH__, configuration)) return LogManager::DisplayError(L"Invalid configuration file. Changes were not saved.\n");
	m_instance.LoadOptions(configuration.GrabOptions);
	return LogManager::DisplayMessage(L"Configuration file reloaded.\n");
}

// Scan the defined pixels until the popup is found, then launch the TryToCaptureTask.
void GrabManager::ManualGrabTask()
{
	m_inputHelper.SetCursorPosition();
	while (m_manualGrab)
	{
		if (CheckPixels())
		{
			wcout << GREEN << L"Pop up detected !\n" << LIGHTGREY;
			m_tryToCapture = true;
			TryToGrabTask();
		}
	}
	m_tryToCapture = false;
}

// Scan the defined pixels until the popup is found, then launch the TryToCaptureTask and stop it as soon as the popup is closed.
void GrabManager::AutoGrabTask()
{
	m_inputHelper.SetCursorPosition();
	while (m_autoGrab)
	{
		if (CheckPixels())
		{
			if(!m_tryToCapture)
			{
				wcout << GREEN << L"Pop up detected !\n" << LIGHTGREY;
				m_tryToCapture = true;
				async (&GrabManager::TryToGrabTask, this);
			}
		}
		else if (m_tryToCapture)
		{
			m_tryToCapture = false;
			wcout << GREEN << L"Pop up closed !\n" << LIGHTRED;
		}
	}
	m_tryToCapture = false;
}

// Check the defined pixels to determine if the popup is displayed.
bool GrabManager::CheckPixels()
{
	bool result = true;
	for (int i = 0; i < m_pixelsToScan; i++)
		result &= m_inputHelper.CheckPixel(m_coordinatesToScan.at(i));
	return result;
}

// Try to grab the land.
void GrabManager::TryToGrabTask()
{
	// delay the grab if required
	if (m_startAfterInterval > 0)
		Sleep(m_startAfterInterval);

	// try to capture until cancellation
	while (m_tryToCapture)
	{
		m_inputHelper.SetCursorPosition();
		for (int i = 0; i < m_clicksPerTry; i++)
			m_inputHelper.LeftClick();
		Sleep(m_tryInterval);
	}
}

// Calculate the start sleep interval.
void GrabManager::CalculateStartSleep()
{
	m_startSleep = m_startAfterInterval;
	if (m_startAfterRandomInterval > 0)
	{
		srand ((unsigned int)time(NULL));
		m_startSleep += rand() % m_startAfterRandomInterval;
	}
}

// Low level hook.
LRESULT CALLBACK GrabManager::LowLevelHook(int nCode, WPARAM wParam, LPARAM lParam)
{
	if (nCode >= HC_ACTION)
	{
		if (wParam == WM_KEYDOWN)
		{
			if(GetAsyncKeyState(VK_CONTROL) & 0x8000)
			{
				KBDLLHOOKSTRUCT* pkbhs = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
				switch(pkbhs->vkCode)
				{
				case VK_F1:
					m_instance.ShowHelp();
					break;
				case VK_F2:
					m_instance.StartManualGrab();
					break;
				case VK_F3:
					m_instance.StopManualGrab();
					break;
				case VK_F4:
					GetAsyncKeyState(VK_SHIFT) & 0x8000 ? exit(0) : m_instance.SwitchAutoGrab();
					break;
				case VK_F5:
					m_instance.SetCursorPosition();
					break;
				case VK_F6:
					m_instance.GetCursorPosition();
					break;
				case VK_F8:
					m_instance.ReloadConfiguration();
					break;
				case VK_ADD:
					GetAsyncKeyState(VK_SHIFT) & 0x8000 ? m_instance.IncreasePixelsToScan() : m_instance.RaiseSensibilityLevel();
					break;
				case VK_SUBTRACT:
					GetAsyncKeyState(VK_SHIFT) & 0x8000 ? m_instance.DecreasePixelsToScan() : m_instance.LowerSensibilityLevel();
					break;
				default:
					break;
				}
			}
		}
	}
	return CallNextHookEx(m_instance.m_hHock, nCode, wParam, lParam);
}